package org.example.aspect;

import cn.hutool.core.thread.threadlocal.NamedThreadLocal;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.annotation.Log;
import org.example.domain.po.ErpLog;
import org.example.domain.po.ErpUser;
import org.example.enums.BusinessStatus;
import org.example.service.ErpLogService;
import org.example.utils.IpUtil;
import org.example.utils.Result;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 日志切面
 */
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {
    
    private final ErpLogService logService;
    
//    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
    
    /**
     * 返回通知
     * @param joinPoint 切点
     * @param controllerLog
     * @param jsonResult
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        // 调用处理日志的方法
        handleLog(joinPoint, controllerLog, null, jsonResult);
        
    }
    
    /**
     * 异常通知
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(pointcut = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        // 调用处理日志的方法
        handleLog(joinPoint, controllerLog, e, null);
    }
    
    /**
     * 处理操作日志的逻辑
     * 方法执行完毕或发生异常时，此方法用于封装和记录日志操作
     * @param joinPoint     切点，用于获取目标方法的信息
     * @param controllerLog 控制器上的日志注解，用于获取方法描述等信息
     * @param e             异常对象，如果方法执行过程中抛出异常
     * @param jsonResult    方法返回的对象，用于日志记录，此参数可能为null
     */
    private void handleLog(JoinPoint joinPoint, Log controllerLog, Exception e, Object jsonResult) {
        try {
            // 获取当前请求的属性，包括HttpServletRequest对象
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            // 如果请求为空，则直接返回，不处理日志
            if (requestAttributes == null) {
                return;
            }
            ServletRequestAttributes http = (ServletRequestAttributes) requestAttributes;
            // 再次获取HttpServletRequest对象
            HttpServletRequest httpServletRequest = http.getRequest();
            
            // 创建ErpLog对象，存储操作日志的信息
            ErpLog erpLog = new ErpLog();
            // 默认设置操作状态为正常
            // ordinal()：返回枚举对象的序数
            erpLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            
            Result resp = (Result) jsonResult;
            
            if (e != null || resp.getCode() != 200){
                // 设置状态为异常
                erpLog.setStatus(BusinessStatus.FAIL.ordinal());
            }
            
            // 获取IP地址
            String ip = IpUtil.getLocalIp();
            erpLog.setClientIp(ip);
            
            ErpUser user = (ErpUser) httpServletRequest.getAttribute("user");
            // 获取用户ID
            erpLog.setUserId(user.getId());
            // 获取租户ID
            erpLog.setTenantId(user.getTenantId());
            // 获取注解中对方法的描述信息
            getControllerMethodDescription(joinPoint, controllerLog, jsonResult, erpLog);
            // 设置创建时间
            erpLog.setCreateTime(new Date());
            // 保存
            logService.save(erpLog);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * 从注解中获取控制器方法的描述信息，并填充到操作日志对象中
     * @param joinPoint 切点对象，用于获取方法名和参数信息
     * @param log 日志注解对象，包含操作模块、操作详情等配置信息
     * @param jsonResult 方法的返回结果，用于判断是否需要记录响应数据
     * @param erpLog 系统操作日志对象，此处将从controllerLog中获取的信息填充到该对象中
     */
    private void getControllerMethodDescription(JoinPoint joinPoint, Log log, Object jsonResult, ErpLog erpLog) throws Exception {
        
        // 设置操作模块
        erpLog.setOperation(log.operation());
        // 设置操作详情
        erpLog.setContent(log.businessType().getValue());
        // 判断是否需要保存请求数据
        if (log.isSaveRequestData()){
            setRequestValue(joinPoint, erpLog,log);
        }
    }
    
    /**
     * 设置操作日志的请求参数
     * @param joinPoint 切点，用于获取方法参数
     * @param erpLog    操作日志对象，用于设置请求参数信息
     * @param log       log注解对象
     */
    private void setRequestValue(JoinPoint joinPoint, ErpLog erpLog,  Log log) throws JsonProcessingException {
        // 获取需要的请求参数
        String parameter = getParameterMap(joinPoint,log);
        // 如果请求参数不为空
        if (StrUtil.isNotEmpty(parameter)){
            erpLog.setContent(erpLog.getContent() + "：" + parameter);
        }
    }
    
    
    /**
     * 获取当前HTTP请求的参数
     */
    private String getParameterMap(JoinPoint joinPoint, Log log) throws JsonProcessingException {
        // Jackson 库的类，用于处理 JSON 数据的序列化和反序列化
        ObjectMapper mapper = new ObjectMapper();
        // joinPoint.getSignature() 获取当前方法的签名，强制转换为 MethodSignature 以便访问方法参数的信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 返回方法的所有参数名，并将其转换为列表 argNames
        List<String> argNames = Arrays.asList(methodSignature.getParameterNames()); // 参数名
        /*
            joinPoint.getArgs()：获取方法执行时提供的参数值的数组。
            argNames.indexOf(log.paramName())：通过从 log 注解中获取的参数名称，找出在 argNames 列表中的索引。
            paramValue：根据参数索引从 joinPoint.getArgs() 中获取相应的参数值。
         */
        Object paramValue = joinPoint.getArgs()[argNames.indexOf(log.paramName())];
        // 判断返回的JSON路径是否有长度，表示是否为空值
        if(StringUtils.hasLength(log.jsonPath())){
            /*
                mapper.writeValueAsString(paramValue)：将参数值序列化为 JSON 字符串。
                JsonPath.read(...)：使用 JsonPath（一个用于查询 JSON 数据的库）从 JSON 字符串中提取某个字段（通过 log.jsonPath() 指定的路径）。
                return read.toString();：返回从 JSON 中读取的值的字符串表示
             */
            Object read = JsonPath.read(mapper.writeValueAsString(paramValue), log.jsonPath());
            return read.toString();
        }else{
            // 如果为空直接返回log注解的paramName
            return paramValue.toString();
        }
    }
}
